package org.xteam.camllight;

import java.lang.reflect.Method;

public class BytecodeDecoder {

	private MachineState state;
	private CodeStream codeStream;

	public BytecodeDecoder(Block globals) {
		state = new MachineState(globals);
	}

	public void decode(byte[] code) {
		codeStream = new CodeStream(code);
		while(true) {
			decodeOne();
		}
	}

	private void decodeOne() {
		try {
			int pc = codeStream.pc();
			InstructionType instType = InstructionType.get(codeStream.getUI8());
			//System.out.println(pc + ": " + instType);
			Method method = getClass().getMethod("do" + instType.name());
			method.invoke(this);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	public void doBRANCH() {
		int pc = codeStream.pc();
		codeStream.goTo(pc + codeStream.getSI16());
	}
	
	public void doBRANCHIF() {
		int pc = codeStream.pc() + codeStream.getSI16();
		if (Value.tagOf(state.getAccu()) != 0) {
			codeStream.goTo(pc);
		}
	}
	
	public void doBRANCHIFEQ() {
		int pc = codeStream.pc();
		int newPc = pc + codeStream.getSI16();
		if (state.getAccu().equals(state.popArgument())) {
			codeStream.goTo(newPc);
		}
	}
	
	public void doBRANCHIFNEQ() {
		int pc = codeStream.pc();
		int newPc = pc + codeStream.getSI16();
		if (! state.getAccu().equals(state.popArgument())) {
			codeStream.goTo(newPc);
		}
	}
	
	public void doBRANCHIFLT() {
		int pc = codeStream.pc();
		int newPc = pc + codeStream.getSI16();
		if (((Integer)state.getAccu()) < ((Integer)state.popArgument())) {
			codeStream.goTo(newPc);
		}
	}
	
	public void doBRANCHIFGT() {
		int pc = codeStream.pc();
		int newPc = pc + codeStream.getSI16();
		if (((Integer)state.getAccu()) > ((Integer)state.popArgument())) {
			codeStream.goTo(newPc);
		}
	}
	
	public void doBRANCHIFLE() {
		int pc = codeStream.pc();
		int newPc = pc + codeStream.getSI16();
		if (((Integer)state.getAccu()) <= ((Integer)state.popArgument())) {
			codeStream.goTo(newPc);
		}
	}
	
	public void doBRANCHIFGE() {
		int pc = codeStream.pc();
		int newPc = pc + codeStream.getSI16();
		if (((Integer)state.getAccu()) >= ((Integer)state.popArgument())) {
			codeStream.goTo(newPc);
		}
	}
	
	public void doBRANCHIFNOT() {
		int pc = codeStream.pc();
		int newPc = pc + codeStream.getSI16();
		int tag = Value.tagOf(state.getAccu());
		if (tag == 0) {
			codeStream.goTo(newPc);
		}
	}
	
	public void doAPPLY() {
		apply();
	}
	
	public void doAPPTERM() {
		appterm();
	}
	
	public void doCHECK_SIGNALS() {
		checkSignals();
	}
	
	public void doCUR() {
		int pc = codeStream.pc();
		int curPc = pc + codeStream.getSI16();
		state.setAccu(new Closure(state.env(), new CodePointer(codeStream, curPc)));
	}
	
	public void doGRAB() {
		int pc = codeStream.pc();
		if (! (state.peekArgument() instanceof Mark)) {
			state.pushArgIntoReturn();
		} else {
			state.heapifyEnv();
			state.setAccu(new Closure(state.env(), new CodePointer(codeStream, pc)));
			state.popArgument(); // pop MARK
			CodePointer pointer = state.popReturnFrame();
			codeStream = pointer.getCodeStream();
		}
	}
	
	public void doRETURN() {
		if (state.peekArgument().equals(Mark.INSTANCE)) {
			state.popCached();
			state.popArgument(); // pop MARK
			CodePointer pointer = state.popReturnFrame();
			codeStream = pointer.getCodeStream();
		} else {
			appterm();
		}
	}
	
	public void doGETGLOBAL() {
		int num = codeStream.getUI16();
		state.setAccu(state.getGlobal(num));
	}
	
	public void doSETGLOBAL() {
		int num = codeStream.getUI16();
		state.setGlobal(num, state.getAccu());
	}
	
	public void doLET() {
		state.pushReturn(state.getAccu());
	}
	
	public void doDUMMY() {
		int size = codeStream.getUI8();
		Block block = new Block(0, size);
		for (int i = 0; i < size; ++i) {
			block.setField(i, Value.valLong(0));
		}
		state.setAccu(block);
	}
	
	public void doUPDATE() {
		Block tmp = (Block) state.popArgument();
		Block accu = (Block) state.getAccu();
		accu.setTag(tmp.tag());
		for (int i = 0; i < tmp.size(); ++i) {
			accu.setField(i, tmp.field(i));
		}
	}
	
	public void doLETREC1() {
		int pc = codeStream.pc() + codeStream.getSI16();
		Closure closure = new Closure(null, new CodePointer(codeStream, pc));
		state.setAccu(closure);
		state.pushReturn(closure);
		state.heapifyEnv();
		closure.setEnv(state.env());
	}
	
	public void doENDLET1() {
		state.endlet(1);
	}
	
	public void doENDLET() {
		int n = codeStream.getUI8();
		state.endlet(n);
	}
	
	public void doACC0() {
		state.setAccu(state.access0());
	}
	
	public void doACC1() {
		state.setAccu(state.access(1));
	}
	
	public void doACC2() {
		state.setAccu(state.access(2));
	}
	
	public void doACC3() {
		state.setAccu(state.access(3));
	}
	
	public void doACC4() {
		state.setAccu(state.access(4));
	}
	
	public void doACC5() {
		state.setAccu(state.access(5));
	}

	public void doACCESS() {
		state.setAccu(state.access(codeStream.getUI8()));
	}
	
	public void doGETFIELD0() {
		state.setAccu(Value.field(state.getAccu(), 0));
	}
	
	public void doGETFIELD1() {
		state.setAccu(Value.field(state.getAccu(), 1));
	}
	
	public void doGETFIELD2() {
		state.setAccu(Value.field(state.getAccu(), 2));
	}
	
	public void doGETFIELD3() {
		state.setAccu(Value.field(state.getAccu(), 3));
	}
	
	public void doGETFIELD() {
		int num = codeStream.getUI8();
		state.setAccu(Value.field(state.getAccu(), num));
	}
	
	public void doSETFIELD0() {
		Value.setField(state.getAccu(), 0, state.popArgument());
		state.setAccu(Atom.get(0));
	}
	
	public void doSETFIELD1() {
		Value.setField(state.getAccu(), 1, state.popArgument());
		state.setAccu(Atom.get(0));
	}
	
	public void doSETFIELD2() {
		Value.setField(state.getAccu(), 2, state.popArgument());
		state.setAccu(Atom.get(0));
	}
	
	public void doSETFIELD3() {
		Value.setField(state.getAccu(), 3, state.popArgument());
		state.setAccu(Atom.get(0));
	}
	
	public void doSETFIELD() {
		int num = codeStream.getUI8();
		Value.setField(state.getAccu(), num, state.popArgument());
		state.setAccu(Atom.get(0));
	}

	public void doATOM0() {
		state.setAccu(Atom.get(0));
	}
	
	public void doATOM1() {
		state.setAccu(Atom.get(1));
	}
	
	public void doATOM2() {
		state.setAccu(Atom.get(2));
	}
	
	public void doATOM3() {
		state.setAccu(Atom.get(3));
	}
	
	public void doATOM4() {
		state.setAccu(Atom.get(4));
	}
	
	public void doATOM5() {
		state.setAccu(Atom.get(5));
	}
	
	public void doATOM6() {
		state.setAccu(Atom.get(6));
	}
	
	public void doATOM7() {
		state.setAccu(Atom.get(7));
	}
	
	public void doATOM8() {
		state.setAccu(Atom.get(8));
	}
	
	public void doATOM9() {
		state.setAccu(Atom.get(9));
	}
	
	public void doATOM() {
		state.setAccu(Atom.get(codeStream.getUI8()));
	}
	
	public void doPUSH() {
		state.pushArgument(state.getAccu());
	}
	
	public void doPUSHMARK() {
		state.pushArgument(Mark.INSTANCE);
	}
	
	public void doPUSH_GETGLOBAL_APPLY() {
		state.pushArgument(state.getAccu());
		int num = codeStream.getUI16();
		state.setAccu(state.getGlobal(num));
		apply();
	}
	
	public void doPUSH_GETGLOBAL_APPTERM() {
		state.pushArgument(state.getAccu());
		int num = codeStream.getUI16();
		state.setAccu(state.getGlobal(num));
		appterm();
	}
	
	public void doPUSHTRAP() {
		int disp = codeStream.getSI16();
		state.pushTrapFrame(new CodePointer(codeStream, codeStream.pc() + disp));
	}
	
	public void doPOPTRAP() {
		state.popTrapFrame();
	}
	
	public void doCONSTBYTE() {
		state.setAccu(codeStream.getUI8());
	}
	
	public void doCONSTSHORT() {
		state.setAccu(codeStream.getSI16());
	}
	
	public void doINCR() {
		Value.setField(state.getAccu(), 0, (Integer)Value.field(state.getAccu(), 0) + 2);
		state.setAccu(Atom.get(0));
	}
	
	public void doDECR() {
		Value.setField(state.getAccu(), 0, (Integer)Value.field(state.getAccu(), 0) - 2);
		state.setAccu(Atom.get(0));
	}
	
	public void doADDINT() {
		state.setAccu(((Integer)state.getAccu()) + ((Integer)state.popArgument()) - 1);
	}
	
	public void doSUBINT() {
		state.setAccu(((Integer)state.getAccu()) - ((Integer)state.popArgument()) - 1);
	}
	
	public void doPREDINT() {
		state.setAccu(((Integer)state.getAccu()) - 2);
	}
	
	public void doMULINT() {
		state.setAccu(1 + Value.longVal((Integer)state.getAccu()) * (((Integer)state.popArgument())-1));
	}
	
	public void doSHIFTLEFTINT() {
		state.setAccu(1 + (((Integer)state.getAccu() - 1) << Value.longVal((Integer)state.popArgument())));
	}
	
	public void doMODINT() {
		int tmp = ((Integer)state.popArgument()) - 1;
		if (tmp == 0) {
			// accu = Atom(ZERO_DIVIDE_EXN);
			throw new RuntimeException("burp");
		}
		state.setAccu(1 + ((Integer)state.getAccu() - 1) % tmp);
	}
	
	public void doC_CALL1() {
		int primNum = codeStream.getUI16();
		Primitive prim = Primitives.get(primNum);
		System.out.println("\t" + prim.getClass().getName() + "("+state.getAccu() + ")");
		state.setAccu(prim.exec(state.getAccu()));
	}
	
	public void doC_CALL2() {
		int primNum = codeStream.getUI16();
		Object arg1 = state.popArgument();
		Primitive prim = Primitives.get(primNum);
		System.out.println("\t" + prim.getClass().getName() + "("+state.getAccu() + ", " + arg1 + ")");
		state.setAccu(prim.exec(state.getAccu(), arg1));
	}
	
	public void doC_CALL3() {
		int primNum = codeStream.getUI16();
		Object arg1 = state.popArgument();
		Object arg2 = state.popArgument();
		Primitive prim = Primitives.get(primNum);
		System.out.println("\t" + prim.getClass().getName() + "("+state.getAccu() + ", " + arg1 + ", " + arg2 + ")");
		state.setAccu(prim.exec(state.getAccu(), arg1, arg2));
	}
	
	public void doC_CALL4() {
		int primNum = codeStream.getUI16();
		Object arg1 = state.popArgument();
		Object arg2 = state.popArgument();
		Object arg3 = state.popArgument();
		Primitive prim = Primitives.get(primNum);
		System.out.println("\t" + prim.getClass().getName() + "(" + state.getAccu() + ", " + arg1 + ", " + arg2 + ", " + arg3 + ")");
		state.setAccu(prim.exec(state.getAccu(), arg1, arg2, arg3));
	}
	
	public void doMAKEVECTOR() {
		int size = Value.longVal((Integer) state.getAccu());
		Object value = state.popArgument();
		if (size == 0) {
			state.setAccu(Atom.get(0));
		} else {
			Block block = new Block(0, size);
			for (int i = 0; i < size; ++i) {
				block.setField(i, value);
			}
			state.setAccu(block);
		}
	}
	
	public void doVECTLENGTH() {
		state.setAccu(Value.valLong(Value.sizeOf(state.getAccu())));
	}
	
	public void doGETVECTITEM() {
		state.setAccu(Value.field(state.getAccu(), Value.longVal((Integer)state.popArgument())));
	}
	
	public void doSETVECTITEM() {
		int num = Value.longVal((Integer)state.popArgument());
		Value.setField(state.getAccu(), num, state.popArgument());
		state.setAccu(Atom.get(0));
	}
	
	public void doSTRINGLENGTH() {
		String str = (String) state.getAccu();
		state.setAccu(Value.valLong(str.length()));
	}

	public void doMAKEBLOCK1() {
		int tag = codeStream.getUI8();
		Block block = new Block(tag, 1);
		block.setField(0, state.getAccu());
		state.setAccu(block);
	}

	public void doMAKEBLOCK2() {
		int tag = codeStream.getUI8();
		Block block = new Block(tag, 2);
		block.setField(0, state.getAccu());
		block.setField(1, state.popArgument());
		state.setAccu(block);
	}

	public void doMAKEBLOCK3() {
		int tag = codeStream.getUI8();
		Block block = new Block(tag, 3);
		block.setField(0, state.getAccu());
		block.setField(1, state.popArgument());
		block.setField(2, state.popArgument());
		state.setAccu(block);
	}

	public void doMAKEBLOCK4() {
		int tag = codeStream.getUI8();
		Block block = new Block(tag, 4);
		block.setField(0, state.getAccu());
		block.setField(1, state.popArgument());
		block.setField(2, state.popArgument());
		block.setField(3, state.popArgument());
		state.setAccu(block);
	}

	public void doMAKEBLOCK() {
		int header = codeStream.getUI32();
		int tag = Header.tagOf(header);
		int size = Header.woSizeOf(header);
		Block block = new Block(tag, size);
		block.setField(0, state.getAccu());
		for (int i = 1; i < size; ++i) {
			block.setField(i, state.popArgument());
		}
		state.setAccu(block);
	}

	private void apply() {
		state.pushReturnFrame(new CodePointer(codeStream, codeStream.pc()));
		state.pushArgIntoReturn();
		CodePointer pointer = state.doApply();
		codeStream = pointer.getCodeStream();
		checkSignals();
	}
	
	private void appterm() {
		state.popCached();
		state.pushArgIntoReturn();
		CodePointer pointer = state.doApply();
		codeStream = pointer.getCodeStream();
		checkSignals();
	}

	private void checkSignals() {
		
	}
	
}
